Skip to content

Unified session list: backend service + endpoint#139

Merged
Ark0N merged 3 commits into
Ark0N:masterfrom
aakhter:cod-160-unified-session-service
Jul 12, 2026
Merged

Unified session list: backend service + endpoint#139
Ark0N merged 3 commits into
Ark0N:masterfrom
aakhter:cod-160-unified-session-service

Conversation

@aakhter

@aakhter aakhter commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

First increment of a read-only "complete + searchable session list" — the backend half. Frontend (rich list UI, persistent modal, live refresh) and polish (pinning, cross-device ordering, name/prompt retention) follow in stacked PRs.

What this adds

  • New src/services/unified-session-service.ts — pure, IO-free, unit-testable:
    • mergeUnifiedSessions() combines five sources — live sessions + persisted (state.json) + lifecycle log + ~/.claude transcript history + mux stats — into one list de-duped by sessionId, with precedence history < lifecycle < persisted < live, a meaningfulness floor that drops bare lifecycle/mux-only noise, and a stable newest-first sort.
    • filterAndPaginate() — case-insensitive q over name/firstPrompt/workingDir/sessionId; total computed before paging; limit clamped to [1, 500].
  • New GET /api/sessions/unified in session-routes.ts — gathers the five sources from ctx (each try/caught so one bad source can't fail the request), feeds the pure service, returns { sessions, total } in the standard ApiResponse envelope. Short-circuits to empty in test mode.

Tests

  • test/services/unified-session-service.test.ts — 12, pure (merge precedence, de-dup, meaningfulness floor, sort, filter, pagination, limit-clamp).
  • test/routes/unified-sessions-routes.test.ts — 4, via app.inject (envelope, pagination, q-search, limit-clamp).

Verification

tsc --noEmit clean · new tests 16/16 · existing session-routes suite 53/53 (no regression) · prettier + eslint clean · build clean.

Read-only and additive — no existing route or behavior changes.

aakhter and others added 3 commits July 1, 2026 13:25
First increment of the read-only "complete + searchable session list".

- New src/services/unified-session-service.ts: mergeUnifiedSessions() combines
  live + persisted (state.json) + lifecycle + ~/.claude transcript history + mux
  stats into one list de-duped by sessionId, with precedence
  history < lifecycle < persisted < live, a meaningfulness floor that drops bare
  lifecycle/mux-only noise, and a stable newest-first sort. Plus
  filterAndPaginate() (case-insensitive q over name/firstPrompt/workingDir/
  sessionId; total before paging; limit clamped [1,500]). No IO — unit-testable.
- New GET /api/sessions/unified in session-routes.ts: gathers the five sources
  from ctx (sessions/store/lifecycle/scanProjectDir/mux, each try/caught), feeds
  the pure service, returns { sessions, total } (ApiResponse envelope). testMode
  short-circuits to empty.

Tests: unified-session-service.test.ts (12, pure) + unified-sessions-routes.test.ts
(4, app.inject).
createInitialRalphTrackerState() stamps lastActivity: Date.now(). The
'should create fresh instances each time' test deep-equaled two factory
results, so two calls straddling a millisecond boundary differed by 1ms
and failed intermittently (e.g. PR Ark0N#139 CI: 1782927694581 vs ...580).

Exclude the dynamic lastActivity from the equality check and assert it
is a number separately, preserving the test's intent (distinct instances
with identical initial field values) without the timing race.
…/mode (PR Ark0N#139)

- Duplicate rows: transcript-history rows are keyed by the Claude
  conversation UUID (.jsonl filename stem), which diverges from the Codeman
  session id for resumed (claudeSessionId = resumeSessionId != id) and
  /clear-respawned sessions, so one conversation surfaced as both a live row
  and a history-only row. mergeUnifiedSessions now builds an alias map
  (claudeSessionId -> Codeman id) from the live + persisted views and
  resolves history/lifecycle keys through it; the route feeds
  SessionState.resumeSessionId as the persisted alias.
- Inverted precedence: SessionLifecycleLog.query() returns entries
  NEWEST-first, but the merge loop unconditionally overwrote name/mode so
  the OLDEST entry in the window won (stale rename/mode). First-seen now
  wins, mirroring the existing lastActivityAt guard.
- Tests: resumed session yields ONE row (service unit + route end-to-end
  with a real transcript fixture); renamed-then-deleted session surfaces
  the NEWEST name/mode. All 4 new tests fail against the pre-fix code.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Ark0N Ark0N merged commit 9204e42 into Ark0N:master Jul 12, 2026
2 checks passed
@Ark0N

Ark0N commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Merged — thank you @aakhter! The unified session service is a great foundation (and PR #146's palette plugs straight into it). Review appended one commit: resumed/respawned sessions now dedupe through a claudeSessionId→session-id alias map, and lifecycle name/mode resolution is newest-wins (query() returns newest-first, so the old loop let the oldest entry win). Both covered with tests. 🙏

Ark0N pushed a commit to aakhter/Codeman that referenced this pull request Jul 12, 2026
…stop Ctrl+K PTY leak, finish shortcut registry (PR Ark0N#146)

- Session Manager (COD-121/192): align _loadSessionManagerList() with the
  merged Ark0N#139 endpoint — map UnifiedSessionItem fields (lastActivityAt
  epoch-ms → lastModified, optional sizeBytes/firstPrompt/name) to the
  history-record shape _buildHistoryItem renders; surface non-2xx /
  error-envelope responses as a visible message instead of a silent
  "No sessions found"; route clicks by liveness (live row → selectSession,
  history row → resumeHistorySession by conversation UUID) via a new
  onActivate option so a live session is never duplicate-resumed
- Ctrl+K double-dispatch: gate the palette chord in
  attachCustomKeyEventHandler (return false on keydown) so xterm never
  writes 0x0b kill-line into the PTY while the palette opens; gate is
  registry-aware so a rebound/disabled palette shortcut restores normal
  terminal Ctrl+K
- Shortcut registry (COD-157) finished per maintainer decision: document
  keydown now dispatches through getShortcutRegistry() +
  matchesShortcutEvent() (legacy SHORTCUTS table removed), honoring
  per-shortcut disable and rebinds incl. the palette chord; overrides
  persist via saveAppSettingsToStorage() (correct device key + cache
  coherence, was orphaned 'codeman:settings'); Shortcuts tab renders on
  open via switchSettingsTab hook; capture uses a persistent listener that
  ignores bare modifier keydowns (combos now capturable) and requires a
  Ctrl/Cmd/Alt chord; settings rows use delegated listeners instead of
  inline onclick (JS-string injection sink) and overrides can no longer
  clobber id/label/action; added the missing row + overlay CSS
- matchesShortcutEvent: reject undeclared extra modifiers (Ctrl+Shift+K
  no longer hijacked from Firefox devtools) while keeping Ctrl/Cmd
  interchangeable; match physical code OR produced key for layout parity
- Registry/dispatch gaps: added restore-terminal-size entry, documented
  Ctrl+Shift+R again in the help modal (test flipped to assert presence),
  Ctrl+?/Alt+? now really open the registry-driven shortcut overlay, and
  Escape closes it
- Palette new-session pick routes through selectQuickStartCase() so the
  searchable combobox, dir display, and lastUsedCase stay in sync
- Removed fork cherry-pick debris: dead _onSessionListMaybeChanged(),
  orphaned .session-row-menu CSS, nonexistent closeMobileHeaderUtilities
  calls
- Tests: functional vm-harness coverage for the unified-list field
  mapping + error state + liveness routing, palette chord shift/disable/
  rebind handling, override persistence round-trip, capture flow, tab
  render hook, and source guards for the PTY gate + registry dispatch

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants